/*
 * EditDistanceGazetteer.java
 *
 */
package edu.columbia.gate.plugins;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import edu.columbia.gate.plugins.AbstractEditDistanceGazetteer.MatchEntryData;

import gate.*;
import gate.corpora.DocumentContentImpl;
import gate.creole.*;
import gate.creole.gazetteer.*;
import gate.persist.PersistenceException;
import gate.security.SecurityException;
import gate.util.*;

/** This component is responsible for doing lists lookup. The implementaion is
 * based on finite state machines.
 * The phrases to be recognised should be listed in a set of files, one for
 * each type of occurences.
 * The gazeteer is build with the information from a file that contains the set
 * of lists (which are files as well) and the associated type for each list.
 * The file defining the set of lists should have the following syntax:
 * each list definition should be written on its own line and should contain:
 * <ol>
 * <li>the file name (required) </li>
 * <li>the major type (required) </li>
 * <li>the minor type (optional)</li>
 * <li>the language(s) (optional) </li>
 * </ol>
 * The elements of each definition are separated by &quot;:&quot;.
 * The following is an example of a valid definition: <br>
 * <code>personmale.lst:person:male:english</code>
 * Each list file named in the lists definition file is just a list containing
 * one entry per line.
 * When this gazetter will be run over some input text (a Gate document) it
 * will generate annotations of type Lookup having the attributes specified in
 * the definition file.
 */
public class EditDistanceFullTextGazetteer extends AbstractEditDistanceGazetteer
{
	public static final String FEATURE_NAME_PREFIX_LENGTH = "prefixLength";
	public static final String FEATURE_NAME_SUFFIX_LENGTH = "suffixLength";
	public static final String FEATURE_NAME_ENTRY_COUNT = "entryCount";
	public Double missingBlankCost;
	
	/**
	 * Build a gazetter using the default lists from the gate resources
	 */
	public EditDistanceFullTextGazetteer()
	{
	}
	/**
	 * This method runs the gazetteer. It assumes that all the needed parameters
	 * are set. If they are not, an exception will be fired.
	 */
	public void execute() throws ExecutionException
	{
		interrupted = false;
		AnnotationSet annotationSet;
		//check the input
		if (document == null)
		{
			throw new ExecutionException("No document to process!");
		}

		sortedVocabulary = new ArrayList<Map.Entry<String,EditDistanceLookup>>();
		sortedVocabulary.addAll(vocabulary.entrySet());
		Collections.sort(sortedVocabulary, new LookupComparator());

		if (annotationSetName == null || annotationSetName.equals(""))
			annotationSet = document.getAnnotations();
		else
			annotationSet = document.getAnnotations(annotationSetName);

		fireStatusChanged("Performing look-up in " + document.getName() + "...");

		ArrayList<Unigram> unigrams = getUnigrams(annotationSet);

		int oldCharIdx = 0;
		int matchedRegionEnd = 0;
		int matchedRegionStart = 0;
		int length = document.getContent().size().intValue();

		MatchEntryData match = null;
		int i = 0;
		while (i < unigrams.size())
		{
			Unigram unigram = unigrams.get(i);

			match = new MatchEntryData();
			match.sourceWord = unigram.word;

			matchedRegionStart = unigram.start;
			matchedRegionEnd = unigrams.get(i).end;
			
			if (lookupInVocabulary(unigrams, i + 1, match, 0, unigram.word.length()))
			{
				matchedRegionStart = unigram.start + match.sourcePrefixLength;
				matchedRegionEnd = unigrams.get(i + match.unigramsMatched - 1).end
						- match.sourceSuffixLength;
				
				// String contentMatched = content.substring(matchedRegionStart, matchedRegionEnd);
				// System.out.println("*" + contentMatched + "*");
				EditDistanceLookup maxLookup = match.lookups.first();
				int priority2 = majorTypesPriorities.get(maxLookup.majorType);
				ArrayList<String> majorTypes = new ArrayList<String>();
				for (EditDistanceLookup lookup : match.lookups)
				{
					majorTypes.add(lookup.majorType);
					int priority1 = majorTypesPriorities.get(lookup.majorType);
					if(priority1 > priority2 
						|| (priority1 == priority2 
								&& ((String) lookup.features.get(FEATURE_NAME_ENTRY_MATCHED)).length() > ((String) maxLookup.features.get(FEATURE_NAME_ENTRY_MATCHED)).length() 
						))
					{
						maxLookup = lookup;
						priority2 = majorTypesPriorities.get(maxLookup.majorType);
					}
				}
				maxLookup = maxLookup.clone();
				String majorTypesStr = "";
				for (String mtype : majorTypes)
				{
					majorTypesStr += mtype + " ";
				}
				maxLookup.majorType = majorTypesStr.trim();
				maxLookup.features.put("string",
						match.sourceWord);
				String prefix = "";
				String suffix = "";
				if(match.sourcePrefixLength > 0)
					prefix = match.sourceWord.substring(0, match.sourcePrefixLength) + " ";
				if(match.sourceSuffixLength > 0)
					suffix = " " + match.sourceWord.substring(match.sourceWord.length() - match.sourceSuffixLength);
				
				maxLookup.features.put(FEATURE_NAME_ENTRY_MATCHED,
						prefix + match.translation + suffix);
				maxLookup.features.put(FEATURE_NAME_ENTRY_COUNT,
						match.entryCount);
				maxLookup.features.put(FEATURE_NAME_EDIT_DISTANCE, new Double(
						match.getEditDistance()));
				maxLookup.features.put(FEATURE_NAME_PREFIX_LENGTH, new Double(
						match.sourcePrefixLength));
				maxLookup.features.put(FEATURE_NAME_SUFFIX_LENGTH, new Double(
						match.sourceSuffixLength));
				createLookups(maxLookup, unigram.start
						, unigrams.get(i + match.unigramsMatched - 1).end, annotationSet);
			}

			//System.out.println(content.substring(matchedRegionStart, matchedRegionEnd));
			//Report percentage of progress 
			if (matchedRegionEnd - oldCharIdx > 256)
			{
				fireProgressChanged((100 * matchedRegionEnd) / length);
				if (isInterrupted())
					throw new ExecutionInterruptedException(
							"The execution of the "
									+ getName()
									+ " gazetteer has been abruptly interrupted!");
			}

			i += match.unigramsMatched;
		}
//		Annotation[] annots = new Annotation[annotationSet.size()];
//		annotationSet.toArray(annots);
//		for(int k=0; k<annots.length; k++)
//		{
//			SimpleAnnotation annot = (SimpleAnnotation) annots[k];
//			long textStart = annot.getStartNode().getOffset().intValue();
//			long textEnd = annot.getEndNode().getOffset().intValue();
//			String normalString = ((String) annot.getFeatures().get("entryMatched"));
//			if(normalString != null)
//			{
//				DocumentContent normalDocCnt = new DocumentContentImpl(normalString);			
//				try
//				{
//					this.getDocument().edit(textStart, textEnd, normalDocCnt);
//				} catch (InvalidOffsetException e)
//				{
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
//			}
//		}

		fireProcessFinished();
		fireStatusChanged("Look-up complete!");
	} // execute

	private boolean lookupInVocabulary(List<Unigram> unigrams, int startIndex,
			MatchEntryData match, double dlDistance, int unigramLength)
	{
		MatchEntryData imatch = null;
		if (match.lookups == null)
			match.lookups = new TreeSet<EditDistanceLookup>();

		// Check if the word was already looked up. If so,
		// take the results from the cache.
		imatch = (MatchEntryData) matchCache.get(match.sourceWord);
		if (imatch != null)
		{
			TreeSet<EditDistanceLookup> tlookups = match.lookups;
			tlookups.addAll(imatch.lookups);
			imatch.copyTo(match);
			match.unigramsMatched = 1; 
			match.lookups = tlookups;
			return match.successful;
		}
		
		// Set very high initial values (since we will search
		// for minimums).
		match.dlDistance = Double.MAX_VALUE * 0.25;
		match.sourcePrefixLength = Integer.MAX_VALUE >> 2;
		match.sourceSuffixLength = Integer.MAX_VALUE >> 2;

		String targetWord = null;
		TreeSet<EditDistanceLookup> lookups = new TreeSet<EditDistanceLookup>();
		lookups.addAll(match.lookups);
		EditDistanceLookup lookup;
		
		// Check if there exists and exact match.
		lookup = vocabulary.get(match.sourceWord);
		if(lookup != null)
		{
			match.sourcePrefixLength = 0;
			match.sourceSuffixLength = 0;
			match.targetPrefixLength = 0;
			match.targetSuffixLength = 0;
			match.unigramsMatched = 1;
			match.entryCount = 1;
			match.dlDistance = 0;
			match.targetWord = match.sourceWord;
			match.translation = lookup.translation;
			match.lookups.add(lookup);
			match.successful = true;
		}
		else
		{
			// There is not an exact match, will try
			// using edit distance.
			imatch = new MatchEntryData(null);
			MatchEntryData originalMatch = match.clone();
			boolean endToEndSuccess = false;
			MatchEntryData suffixMatch;
			int iUniLength;
			for (Map.Entry<String, EditDistanceLookup> mapEntry : sortedVocabulary)
			{
				iUniLength = unigramLength;
				targetWord = mapEntry.getKey();
				originalMatch.copyTo(imatch);
				imatch.targetWord = targetWord;
				lookup = mapEntry.getValue();
				imatch.translation = lookup.translation;
				imatch.lookups.clear();
				imatch.lookups.add(lookup);
				imatch.entryCount = 1;
	
				// Get the edit distance and optimal alignment. 
				damerauLevenshteinDistance(imatch);
				
				// Be optimistic and ignore dlDistance due extra chars at the beginning or end of the word.
				boolean successfulRecursion = false;

				// Ignore extra chars at the beginning of the word.
				if(imatch.sourcePrefixLength > 0)
					imatch.dlDistance -= imatch.sourcePrefixLength - this.missingBlankCost;

				
				double blankCosts = 0;
				iUniLength -= imatch.sourceWord.length();
				int uniIndex = startIndex;
				for(;imatch.targetSuffixLength > 0
						&& uniIndex < unigrams.size(); uniIndex ++)
				{
					Unigram unigram = unigrams.get(uniIndex);

					if(imatch.targetWord.charAt(imatch.targetWord.length() - imatch.targetSuffixLength) == ' ')
						imatch.sourceWord += " " + unigram.word;
					else
					{
						imatch.sourceWord += unigram.word;
						blankCosts += missingBlankCost;
					}

					imatch.unigramsMatched ++;
					damerauLevenshteinDistance(imatch);

					imatch.dlDistance += blankCosts; 
				}
				iUniLength += imatch.sourceWord.length();

				imatch.dlDistance -= imatch.sourceSuffixLength;
				if( imatch.sourceSuffixLength > 0 
						&& imatch.getEditDistance() < maxEditDistance
						&& ((double) imatch.dlDistance + dlDistance) / ((double) iUniLength) < maxEditDistance
						&& ((double) imatch.dlDistance + dlDistance) / ((double) iUniLength) < match.getEditDistance()
					)
				{
					suffixMatch = imatch.clone();
					suffixMatch.lookups.clear();
					suffixMatch.unigramsMatched = 1;
					suffixMatch.hasEndingTransposition = false;
					
					suffixMatch.sourceWord = suffixMatch.sourceWord
							.substring(suffixMatch.sourceWord.length()
									- suffixMatch.sourceSuffixLength);
					boolean suffixSuccess = lookupInVocabulary(unigrams, uniIndex, suffixMatch, dlDistance + imatch.dlDistance, iUniLength);
					
					if(imatch.hasEndingTransposition) 
					{
						MatchEntryData suffixMatch2 = imatch.clone();
						suffixMatch2.lookups.clear();
						suffixMatch2.unigramsMatched = 1;
						suffixMatch2.hasEndingTransposition = false;

						suffixMatch2.sourceSuffixLength ++;

						suffixMatch2.sourceWord = suffixMatch2.sourceWord
								.substring(suffixMatch2.sourceWord.length()
										- suffixMatch2.sourceSuffixLength);
						
						suffixSuccess |= lookupInVocabulary(unigrams, uniIndex, suffixMatch2, dlDistance + imatch.dlDistance, iUniLength);
						suffixMatch = suffixMatch2.getEditDistance() < suffixMatch.getEditDistance() ? suffixMatch2 : suffixMatch;
					}

					if (suffixSuccess)
					{
						imatch.unigramsMatched += suffixMatch.unigramsMatched - 1;
						imatch.entryCount += suffixMatch.entryCount;
						imatch.targetWord += " " + suffixMatch.targetWord;
						imatch.translation += " " + suffixMatch.translation;
						imatch.sourceSuffixLength = suffixMatch.sourceSuffixLength;
						imatch.targetSuffixLength = suffixMatch.targetSuffixLength;
						imatch.dlDistance += suffixMatch.dlDistance + this.missingBlankCost;
						imatch.lookups.addAll(suffixMatch.lookups);
						successfulRecursion = true;
					}					
				}

				if(imatch.sourceSuffixLength > 0 && dlDistance == 0)
					imatch.dlDistance += this.missingBlankCost;
				else
					imatch.dlDistance += imatch.sourceSuffixLength;
				
				if(imatch.sourceSuffixLength > 0 && imatch.sourcePrefixLength > 0)
					imatch.dlDistance += 2*imatch.sourceSuffixLength;
				
	
				if (imatch.getEditDistance() <= maxEditDistance 
					&& ((imatch.getEditDistance() < match.getEditDistance()
							&& imatch.sourceSuffixLength + imatch.sourcePrefixLength <=  match.sourceSuffixLength + match.sourcePrefixLength)
						|| (dlDistance == 0 
								&& (imatch.sourceSuffixLength + imatch.sourcePrefixLength <  match.sourceSuffixLength + match.sourcePrefixLength
						|| (imatch.getEditDistance() == match.getEditDistance()
								&& imatch.sourceSuffixLength + imatch.sourcePrefixLength ==  match.sourceSuffixLength + match.sourcePrefixLength
								&& imatch.sourcePrefixLength < match.sourcePrefixLength)
						|| (imatch.getEditDistance() == match.getEditDistance()
								&& imatch.sourceSuffixLength + imatch.sourcePrefixLength ==  match.sourceSuffixLength + match.sourcePrefixLength
								&& imatch.sourcePrefixLength == match.sourcePrefixLength
								&& imatch.translation.replaceAll(" +", "").length() > match.translation.replaceAll(" +", "").length()))))
					)
				{
					if(successfulRecursion && imatch.sourceSuffixLength == 0 && imatch.sourcePrefixLength == 0)
						endToEndSuccess = true;
					imatch.copyTo(match);
					match.successful = true;
				}				
			}
			lookups.addAll(match.lookups);
			match.lookups = lookups;
			matchCache.put(match.sourceWord, match);
		}
		return match.successful;
	}

	void damerauLevenshteinDistance(MatchEntryData matchEntryData)
	{
		String str1 = matchEntryData.targetWord;
		String str2 = matchEntryData.sourceWord;

		int lenStr1 = str1.length();
		int lenStr2 = str2.length();
		matchEntryData.sourcePrefixLength = 0;
		matchEntryData.sourceSuffixLength = 0;
		matchEntryData.targetPrefixLength = 0;
		matchEntryData.targetSuffixLength = 0;

		// d is a table with lenStr1+1 rows and lenStr2+1 columns
		int[][] d = new int[lenStr1 + 1][lenStr2 + 1];
		int substCost = 1, insertCost = 1, deletCost = 1, transCost = 1;
		int scost, tcost;

		// i and j are used to iterate over str1 and str2
		int i, j;

		for (i = 0; i <= lenStr1; i++)
			d[i][0] = i;
		for (j = 1; j <= lenStr2; j++)
			d[0][j] = j;

		for (i = 1; i <= lenStr1; i++)
		{
			for (j = 1; j <= lenStr2; j++)
			{
				if (str1.charAt(i - 1) == str2.charAt(j - 1))
				{
					tcost = 0;
					scost = 0;
				} else
				{
					scost = substCost;
					tcost = transCost;
				}
				int deletion = d[i - 1][j] + deletCost; // deletion
				int insertion = d[i][j - 1] + insertCost; // insertion
				int substitution = d[i - 1][j - 1] + scost; // substitution
				// Take the minimum
				d[i][j] = (deletion <= insertion ? deletion : insertion) <= substitution ? (deletion <= insertion ? deletion
						: insertion)
						: substitution;

				// transposition
				if (i > 1 && j > 1 && str1.charAt(i - 1) == str2.charAt(j - 2)
						&& str1.charAt(i - 2) == str2.charAt(j - 1))
				{
					// Take the minimum
					d[i][j] = d[i][j] <= (d[i - 2][j - 2] + tcost) ? d[i][j]
							: (d[i - 2][j - 2] + tcost);
				}
			}
		}

		i = lenStr1;
		j = lenStr2;
		int ssl1 = 0, tsl1 = 0, spl1 = 0, tpl1 = 0;
		// Get the prefix and suffix lengths
		while (i > 0 && j > 0)
		{
			int score = d[i][j];
			int scorediag = d[i - 1][j - 1];
			int scoreleft = d[i][j - 1];
			int scoreup = d[i - 1][j];

			if (i == lenStr1)
			{
				// insertion
				if (score == scoreleft + insertCost)
				{
					ssl1++;
					j--;
				}
				// deletion
				else if (score == scoreup + deletCost)
				{
					if (j == lenStr2)
						tsl1++;
					i--;
				}
				// substitution, transposition or transcription 
				else
				{
					i--;
					j--;
				}
			} 
			else if (j == lenStr2)
			{
				// deletion
				if (score == scoreup + deletCost)
				{
					tsl1++;
					i--;
				} 
				// insertion
				else if (score == scoreleft + insertCost)
				{
					if (i == lenStr1)
						ssl1++;
					j--;
				}
				// substitution, transposition or transcription
				else
				{
					i--;
					j--;
				}
			} 
			else
			{
				// transcription
				if (score == scorediag
						&& str1.charAt(i - 1) == str2.charAt(j - 1))
				{
					i--;
					j--;
				}
				// substitution or transposition
				else if ((score == scorediag + substCost || score == scorediag + transCost) 
						&& str1.charAt(i - 1) != str2.charAt(j - 1))
				{
					i--;
					j--;					
				}
				// insertion
				else if (score == scoreleft + insertCost)
				{
					j--;
				} 
				// deletion
				else if (score == scoreup + deletCost)
				{
					i--;
				} 
				// ...and something wierd that should never happen...
				else
				{
					i--;
					j--;
				}
			}
		}
		spl1 = j; tpl1 = i;
		
		
		// And now, we handle the case when a large prefix should be
		// found, but we didn't find it because our edit priorities
		// were wrong up there because we were looking for a large
		// suffix instead.
		if(ssl1 > 0 || tsl1 > 0)
		{
			i = lenStr1;
			j = lenStr2;
			int ssl2 = 0, tsl2 = 0, spl2 = 0, tpl2 = 0;
			// Get the prefix and suffix lengths
			while (i > 0 && j > 0)
			{
				int score = d[i][j];
				int scorediag = d[i - 1][j - 1];
				int scoreleft = d[i][j - 1];
				int scoreup = d[i - 1][j];
				
				// transcription
				if (score == scorediag
						&& str1.charAt(i - 1) == str2.charAt(j - 1))
				{
					i--;
					j--;
				}
				// substitution or transposition
				else if ((score == scorediag + substCost || score == scorediag + transCost) 
						&& str1.charAt(i - 1) != str2.charAt(j - 1))
				{
					i--;
					j--;					
				}
				// deletion
				else if (score == scoreup + deletCost)
				{
					if (j == lenStr2)
						tsl2++;
					i--;
				} 
				// insertion
				else if (score == scoreleft + insertCost)
				{
					if (i == lenStr1)
						ssl2++;
					j--;
				} 
				// ...and something wierd that should never happen...
				else
				{
					i--;
					j--;
				}
			}
			spl2 = j; tpl2 = i;
			
			if(spl2 + ssl2 > spl1 + ssl1 || tpl2 + tsl2 > tpl1 + tsl1)
			{
				tpl1 = tpl2; tsl1 = tsl2;
				spl1 = spl2; ssl1 = ssl2;
			}
				
		}
		
		matchEntryData.sourcePrefixLength = spl1;
		matchEntryData.targetPrefixLength = tpl1;
		matchEntryData.sourceSuffixLength = ssl1;
		matchEntryData.targetSuffixLength = tsl1;
		// Handle the case when a transposition of the last two characters
		// of the source string reduces the length of the source suffix.
		// This may be undesirable.
		if(matchEntryData.sourceSuffixLength > 0 
			&& lenStr2 - matchEntryData.sourceSuffixLength - 2 >= 0
			&& lenStr1 - 2 >= 0
			&& str2.charAt(lenStr2 - matchEntryData.sourceSuffixLength - 2) == str1.charAt(lenStr1 - 1)
			&& str2.charAt(lenStr2 - matchEntryData.sourceSuffixLength - 1) == str1.charAt(lenStr1 - 2)
			&& d[lenStr1][lenStr2 - matchEntryData.sourceSuffixLength] == d[lenStr1 - 2][lenStr2 - matchEntryData.sourceSuffixLength - 2] + transCost)
		{
			matchEntryData.hasEndingTransposition=true;
		}
		
		matchEntryData.dlDistance = (double) d[lenStr1][lenStr2];
	}

	/**
	 * Creates the EditDistanceLookup annotations according to a gazetteer match.
	 * @param matchingState the final EditDistanceFSMState that was reached while matching.
	 * @param matchedRegionStart the start of the matched text region.
	 * @param matchedRegionEnd the end of the matched text region.
	 * @param annotationSet the annotation set where the new annotations should
	 * be added.
	 */
	protected void createLookups(EditDistanceLookup currentLookup,
			long matchedRegionStart, long matchedRegionEnd,
			AnnotationSet annotationSet)
	{
		FeatureMap fm = Factory.newFeatureMap();
		fm.put(LOOKUP_MAJOR_TYPE_FEATURE_NAME, currentLookup.majorType);
		if (null != currentLookup.oClass && null != currentLookup.ontology)
		{
			fm.put(LOOKUP_CLASS_FEATURE_NAME, currentLookup.oClass);
			fm.put(LOOKUP_ONTOLOGY_FEATURE_NAME, currentLookup.ontology);
		}

		if (null != currentLookup.minorType)
			fm.put(LOOKUP_MINOR_TYPE_FEATURE_NAME, currentLookup.minorType);
		if (null != currentLookup.features)
		{
			fm.putAll(currentLookup.features);
		}
		try
		{
			annotationSet.add(new Long(matchedRegionStart), new Long(
					matchedRegionEnd), LOOKUP_ANNOTATION_TYPE, fm);
		} catch (InvalidOffsetException ioe)
		{
			throw new GateRuntimeException(ioe.toString());
		}
	}

	/** A set containing all the states of the FSM backing the gazetteer
	 */
	protected Set fsmStates;

	/**lookup <br>
	 * @param singleItem a single string to be looked up by the gazetteer
	 * @return set of the Lookups associated with the parameter*/
	public Set lookup(String singleItem)
	{
		Set set = new HashSet();
		singleItem = singleItem.toUpperCase();
		MatchEntryData match = new MatchEntryData(singleItem);
		ArrayList<Unigram> unigrams = new ArrayList<Unigram>();
		unigrams.add(new Unigram(singleItem, 0, singleItem.length() - 1));
		if (lookupInVocabulary(unigrams, 0, match, 0 , singleItem.length()))
		{
			set.addAll(match.lookups);
		}
		return set;
	}

	public boolean remove(String singleItem)
	{
		EditDistanceLookup lookup = vocabulary.remove(singleItem.toUpperCase());
		return (lookup == null);
	}

	public boolean add(String singleItem, Lookup lookup)
	{
		EditDistanceLookup lookup2;
		if (lookup instanceof EditDistanceLookup)
			lookup2 = (EditDistanceLookup) lookup;
		else
			lookup2 = new EditDistanceLookup(lookup);

		singleItem = singleItem.toUpperCase().replaceAll("\\s+", " ");
		vocabulary.put(singleItem, lookup2);

		return true;
	}

	/**
	 * @return the maxEditDistance
	 */
	public Double getMaxEditDistance()
	{
		return new Double(maxEditDistance);
	}

	/**
	 * @param maxEditDistance the maxEditDistance to set
	 */
	public void setMaxEditDistance(Double maxEditDistance)
	{
		this.maxEditDistance = maxEditDistance.doubleValue();
	}

	private class MatchEntryData extends AbstractEditDistanceGazetteer.MatchEntryData
	{
		public String translation;
		public TreeSet<EditDistanceLookup> lookups;
		public int sourcePrefixLength;
		public int sourceSuffixLength;
		public int targetPrefixLength;
		public int targetSuffixLength;
		public int unigramsMatched;
		public int entryCount;
		public boolean hasEndingTransposition;
		public boolean successful;

		public MatchEntryData(String sourceWord)
		{
			super(sourceWord);
		}

		public MatchEntryData()
		{
			reset();
		}

		public void copyTo(EditDistanceFullTextGazetteer.MatchEntryData match)
		{
			super.copyTo(match);
			match.lookups = (TreeSet<EditDistanceLookup>) this.lookups.clone();
			match.sourcePrefixLength = this.sourcePrefixLength;
			match.sourceSuffixLength = this.sourceSuffixLength;
			match.targetPrefixLength = this.targetPrefixLength;
			match.targetSuffixLength = this.targetSuffixLength;
			match.unigramsMatched = this.unigramsMatched;
			match.entryCount = this.entryCount;
			match.translation = this.translation;
			match.hasEndingTransposition = this.hasEndingTransposition;
			match.successful = this.successful;
		}

		public EditDistanceFullTextGazetteer.MatchEntryData clone()
		{
			MatchEntryData match = new MatchEntryData();
			this.copyTo(match);
			return match;
		}

		public void trimDlDistance()
		{
			this.dlDistance -= this.sourceSuffixLength;
			this.dlDistance -= this.targetSuffixLength;
		}

		public void restoreDlDistance()
		{
			this.dlDistance += this.sourceSuffixLength;
			this.dlDistance += this.targetSuffixLength;
		}

		public void reset()
		{
			super.reset();
			if(lookups != null) lookups.clear();
			translation = ""; 
			sourcePrefixLength = 0;
			sourceSuffixLength = 0;
			targetPrefixLength = 0;
			targetSuffixLength = 0;
			unigramsMatched = 1;
			entryCount = 1;
			hasEndingTransposition = false;
			successful = false;
		}

		public double getTrimmedDlDistance()
		{
			return this.dlDistance - ((double) (this.sourcePrefixLength + this.sourceSuffixLength));
		}
		public double getTrimmedEditDistance()
		{
			double targetWordLength = targetWord == null || targetWord.isEmpty() ? 1 : targetWord.length();
			double sourceWordLength = sourceWord == null || sourceWord.isEmpty() ? 1 : sourceWord.length(); 
			return ((double) this.getTrimmedDlDistance()) / Math.min(sourceWordLength, targetWordLength);
		}
		
		public int getUnmatchedTextLength()
		{
			return this.sourcePrefixLength + this.sourceSuffixLength
			+ this.targetPrefixLength + this.targetSuffixLength;
		}
		
		public double getEditDistance()
		{
			double targetWordLength = targetWord == null || targetWord.isEmpty() ? 1 : targetWord.length();
			double sourceWordLength = sourceWord == null || sourceWord.isEmpty() ? 1 : sourceWord.length();
			
			return (((double) this.dlDistance)) / Math.min(sourceWordLength, targetWordLength);
		}
	}

	public Double getMissingBlankCost()
	{
		return missingBlankCost;
	}
	public void setMissingBlankCost(Double missingBlankCost)
	{
		this.missingBlankCost = missingBlankCost;
	}

} // EditDistanceGazetteer
